﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using XNAGameFeatures.BasicGame.Screens;
using XNAGameFeatures.Features;
using XNAGameFeatures.Input;
using XNAGameFeatures.Input.Device;

namespace XNAGameFeatures.BasicGame
{
    /// <summary>
    /// The screen manager is a component which manages all screens of the current game.
    /// It is a easy way to create and manage screen during the game.
    /// </summary>
    public class ScreenManager : DrawableGameComponent
    {

        #region Fields

        private readonly List<Screen> _screens = new List<Screen>();
        private readonly List<Screen> _screensToUpadte = new List<Screen>();
        private readonly List<Screen> _screensToRemove = new List<Screen>();
        private InputManager _input = InputManager.Instance;
        private bool _init;

        public GraphicsDeviceManager Graphics { get; set; }

        private readonly dynamic _fontManager;

        public FontManager FontManager
        {
            get { return _fontManager; }
        }
        

        public InputManager Input
        {
            set { _input = value; }
        }

        public int WidthResolution
        {
            get { return Graphics.PreferredBackBufferWidth; }
            set { Graphics.PreferredBackBufferWidth = value; }
        }

        public int HeightResolution
        {
            get { return Graphics.PreferredBackBufferHeight; }
            set { Graphics.PreferredBackBufferHeight = value; }
        }

        public bool IsFullScreen
        {
            get { return Graphics.IsFullScreen; }
            set { Graphics.IsFullScreen = value; }
        }

        public SpriteBatch SpriteBatch { get; set; }

        readonly Dictionary<String, String> _fonts = new Dictionary<string, string>();

        #endregion

        #region Update and Draw


        /// <summary>
        /// Called when the <see cref="ScreenManager"/> needs to be drawn.
        /// </summary>
        /// <param name="gameTime">Time passed since the last call to Draw.</param>
        public override void Draw(GameTime gameTime)
        {
            foreach (var screen in _screens)
            {
                screen.Draw(gameTime);
            }
        }

        /// <summary>
        /// Called when the <see cref="ScreenManager"/> needs to be updated.
        /// </summary>
        /// <param name="gameTime">Time elapsed since the last call to Update</param>
        public override void Update(GameTime gameTime)
        {
            _input.Update();

            _screensToUpadte.Clear();

            foreach (var screen in _screens)
                _screensToUpadte.Insert(0, screen);

            bool otherScreenTop = false;
            foreach (var screen in _screensToUpadte)
            {
                screen.Update(gameTime);

                
                if (!otherScreenTop || screen.GetType() == typeof(GameScreen))
                {
                    screen.HandleInput(_input);

                    otherScreenTop = true;
                }
            }

            RemoveScreens();

        }

        private void RemoveScreens()
        {
            foreach (Screen screen in _screensToRemove)
            {
                if (_init)
                {
                    screen.UnloadContent();
                }

                _screens.Remove(screen);
                _screensToUpadte.Remove(screen);
            }
            _screensToRemove.Clear();
        }

        #endregion

        #region Init

        /// <summary>
        /// Initializes a new instance of the <see cref="ScreenManager"/> class.
        /// </summary>
        /// <param name="game">The Game that the game component should be attached to.</param>
        /// <param name="mouseActive"></param>
        public ScreenManager(Game game, bool mouseActive = false)
            : base(game)
        {
            _fontManager = Features.FontManager.Instance;
            Graphics = new GraphicsDeviceManager(game);
            if (mouseActive)
            {
                MouseCustomisation.LoadCustomCursor(game);
                game.IsMouseVisible = true;
            }
        }

        /// <summary>
        /// Initializes the component.
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();

            _init = true;
        }

        /// <summary>
        /// Called when graphics resources need to be loaded.
        /// </summary>
        protected override void LoadContent()
        {
            SpriteBatch = new SpriteBatch(GraphicsDevice);

            foreach (KeyValuePair<string, string> font in _fonts)
            {
                ((FontManager) FontManager).AddFont(font.Key, Game.Content.Load<SpriteFont>(font.Value));
            }

            foreach (var screen in _screens)
            {
                screen.LoadContent();
            }
        }

        /// <summary>
        /// Called when graphics resources need to be unloaded.
        /// </summary>
        protected override void UnloadContent()
        {
            foreach (var screen in _screens)
            {
                screen.UnloadContent();
            }
        }

        #endregion

        #region ScreenManger Methods

        public void AddFontToLoad(String name, String path)
        {
            if (!_init)
               _fonts.Add(name, path);
            else
                throw new InvalidOperationException("You cannot add font during the game.");
        }

        /// <summary>
        /// Adds a new screen
        /// </summary>
        /// <param name="screen">Screen to add to the ScreenManager.</param>
        public void AddScreen(Screen screen)
        {
            screen.ScreenManager = this;

            if (_init && !screen.IsContentLoaded)
            {
                screen.LoadContent();
            }

            _screens.Add(screen);
        }

        /// <summary>
        /// Removes a screen.
        /// </summary>
        /// <param name="screen">Screen to remove from the ScreenManager.</param>
        public void RemoveScreen(Screen screen)
        {
            //if (_init)
            //{
            //    screen.UnloadContent();
            //}

            _screensToRemove.Add(screen);
            //_screens.Remove(screen);
            //_screensToUpadte.Remove(screen);
        }

        /// <summary>
        /// Removes the last screen of the ScreenManager.
        /// </summary>
        public void RemoveLastScreen()
        {
            //if (_init)
            //{
            //    _screens[_screens.Count - 1].UnloadContent();
            //}
            _screensToRemove.Add(_screens[_screens.Count - 1]);

            //_screensToUpadte.Remove(_screens[_screens.Count - 1]);
            //_screens.RemoveAt(_screens.Count - 1);
        }

        #endregion

    }
}
